// Copyright (c) Facebook, Inc. and its affiliates.
// All rights reserved.
//
// Copyright 2022 Google LLC
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

#include "xnnpack/log.h"

#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// OS-specific headers.
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#if defined(__ANDROID__)
#include <android/log.h>
#endif
#if defined(__hexagon__)
#define FARF_LOW 1
#include <HAP_farf.h>
#endif

#ifndef XNN_LOG_TO_STDIO
#if defined(__ANDROID__) || defined(__hexagon__)
#define XNN_LOG_TO_STDIO 0
#else
#define XNN_LOG_TO_STDIO 1
#endif
#endif

/* Messages up to this size are formatted entirely on-stack, and don't allocate
 * heap memory */
#define XNN_LOG_STACK_BUFFER_SIZE 1024

#ifdef _WIN32
#define XNN_LOG_NEWLINE_LENGTH 2

#define XNN_LOG_STDERR STD_ERROR_HANDLE
#define XNN_LOG_STDOUT STD_OUTPUT_HANDLE
#elif defined(__hexagon__)
#define XNN_LOG_NEWLINE_LENGTH 1

#define XNN_LOG_STDERR 0
#define XNN_LOG_STDOUT 0
#else
#define XNN_LOG_NEWLINE_LENGTH 1

#define XNN_LOG_STDERR STDERR_FILENO
#define XNN_LOG_STDOUT STDOUT_FILENO
#endif

#if XNN_LOG_TO_STDIO
static void xnn_vlog(int output_handle, const char* prefix,
                     size_t prefix_length, const char* format, va_list args) {
  char stack_buffer[XNN_LOG_STACK_BUFFER_SIZE];
  char* heap_buffer = NULL;
  char* out_buffer = &stack_buffer[0];

  /* The first call to vsnprintf will clobber args, thus need a copy in case a
   * second vsnprintf call is needed */
  va_list args_copy;
  va_copy(args_copy, args);

  memcpy(stack_buffer, prefix, prefix_length * sizeof(char));
  assert((prefix_length + XNN_LOG_NEWLINE_LENGTH) * sizeof(char) <=
         XNN_LOG_STACK_BUFFER_SIZE);

  const int format_chars =
      vsnprintf(&stack_buffer[prefix_length],
                XNN_LOG_STACK_BUFFER_SIZE -
                    (prefix_length + XNN_LOG_NEWLINE_LENGTH) * sizeof(char),
                format, args);
  if (format_chars < 0) {
    /* Format error in the message: silently ignore this particular message. */
    goto cleanup;
  }
  const size_t format_length = (size_t)format_chars;
  if ((prefix_length + format_length + XNN_LOG_NEWLINE_LENGTH) * sizeof(char) >
      XNN_LOG_STACK_BUFFER_SIZE) {
    /* Allocate a buffer on heap, and vsnprintf to this buffer */
    const size_t heap_buffer_size =
        (prefix_length + format_length + XNN_LOG_NEWLINE_LENGTH) * sizeof(char);
#if _WIN32
    heap_buffer = HeapAlloc(GetProcessHeap(), 0, heap_buffer_size);
#else
    heap_buffer = malloc(heap_buffer_size);
#endif
    if (heap_buffer == NULL) {
      goto cleanup;
    }

    /* Copy pre-formatted prefix into the on-heap buffer */
    memcpy(heap_buffer, prefix, prefix_length * sizeof(char));
    vsnprintf(&heap_buffer[prefix_length],
              (format_length + XNN_LOG_NEWLINE_LENGTH) * sizeof(char), format,
              args_copy);
    out_buffer = heap_buffer;
  }
#ifdef _WIN32
  out_buffer[prefix_length + format_length] = '\r';
  out_buffer[prefix_length + format_length + 1] = '\n';

  DWORD bytes_written;
  WriteFile(
      GetStdHandle((DWORD)output_handle), out_buffer,
      (prefix_length + format_length + XNN_LOG_NEWLINE_LENGTH) * sizeof(char),
      &bytes_written, NULL);
#else
  out_buffer[prefix_length + format_length] = '\n';

  ssize_t bytes_written = write(
      output_handle, out_buffer,
      (prefix_length + format_length + XNN_LOG_NEWLINE_LENGTH) * sizeof(char));
  (void)bytes_written;
#endif

cleanup:
#ifdef _WIN32
  HeapFree(GetProcessHeap(), 0, heap_buffer);
#else
  free(heap_buffer);
#endif
  va_end(args_copy);
}
#elif defined(__ANDROID__) && XNN_LOG_LEVEL > XNN_LOG_NONE
static const char xnnpack_module[] = "XNNPACK";
#endif

#if XNN_LOG_LEVEL >= XNN_LOG_DEBUG
void xnn_vlog_debug(const char* format, va_list args) {
#if XNN_LOG_TO_STDIO
  static const char debug_prefix[] = "Debug (XNNPACK): ";
  xnn_vlog(XNN_LOG_STDOUT, debug_prefix, 17, format, args);
#elif defined(__hexagon__)
  FARF(LOW, format, args);
#elif defined(__ANDROID__)
  __android_log_vprint(ANDROID_LOG_DEBUG, xnnpack_module, format, args);
#else
#error "Platform-specific implementation required"
#endif
}
#endif

#if XNN_LOG_LEVEL >= XNN_LOG_INFO
void xnn_vlog_info(const char* format, va_list args) {
#if XNN_LOG_TO_STDIO
  static const char info_prefix[] = "Note (XNNPACK): ";
  xnn_vlog(XNN_LOG_STDERR, info_prefix, 16, format, args);
#elif defined(__hexagon__)
  FARF(MEDIUM, format, args);
#elif defined(__ANDROID__)
  __android_log_vprint(ANDROID_LOG_INFO, xnnpack_module, format, args);
#else
#error "Platform-specific implementation required"
#endif
}
#endif

#if XNN_LOG_LEVEL >= XNN_LOG_WARNING
void xnn_vlog_warning(const char* format, va_list args) {
#if XNN_LOG_TO_STDIO
  static const char warning_prefix[] = "Warning in XNNPACK: ";
  xnn_vlog(XNN_LOG_STDERR, warning_prefix, 20, format, args);
#elif defined(__hexagon__)
  FARF(HIGH, format, args);
#elif defined(__ANDROID__)
  __android_log_vprint(ANDROID_LOG_WARN, xnnpack_module, format, args);
#else
#error "Platform-specific implementation required"
#endif
}
#endif

#if XNN_LOG_LEVEL >= XNN_LOG_ERROR
void xnn_vlog_error(const char* format, va_list args) {
#if XNN_LOG_TO_STDIO
  static const char error_prefix[] = "Error in XNNPACK: ";
  xnn_vlog(XNN_LOG_STDERR, error_prefix, 18, format, args);
#elif defined(__hexagon__)
  FARF(ERROR, format, args);
#elif defined(__ANDROID__)
  __android_log_vprint(ANDROID_LOG_ERROR, xnnpack_module, format, args);
#else
#error "Platform-specific implementation required"
#endif
}
#endif

#if XNN_LOG_LEVEL >= XNN_LOG_FATAL
void xnn_vlog_fatal(const char* format, va_list args) {
#if XNN_LOG_TO_STDIO
  static const char fatal_prefix[] = "Fatal error in XNNPACK: ";
  xnn_vlog(XNN_LOG_STDERR, fatal_prefix, 24, format, args);
#elif defined(__hexagon__)
  FARF(FATAL, format, args);
#elif defined(__ANDROID__)
  __android_log_vprint(ANDROID_LOG_FATAL, xnnpack_module, format, args);
#else
#error "Platform-specific implementation required"
#endif
}
#endif
